package com.zw.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zw.common.constant.product.CategoryConstant;
import com.zw.common.entity.product.CategoryEntity;
import com.zw.gulimall.product.service.CategoryBrandRelationService;
import com.zw.gulimall.product.vo.Catalog2VO;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zw.common.utils.PageUtils;
import com.zw.common.utils.Query;

import com.zw.gulimall.product.dao.CategoryDao;
import com.zw.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> list = this.list();
        List<CategoryEntity> parentList = list.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(item -> {
                    item.setChildren(getChildRens(item, list));
                    return item;
                }).sorted((a1, a2) -> {
                    return (a1.getSort() == null ? 0 : a1.getSort()) - (a2.getSort() == null ? 0 : a2.getSort());
                }).collect(Collectors.toList());

        return parentList;
    }

    @Override
    public void removeBatchByIds(List<Long> catIds) {
        //TODO:  检查当前删除的菜单，是否有其他的地方引用
        baseMapper.deleteBatchIds(catIds);
    }

    @Override
    public Long[] findCatlogPath(Long catelogId) {
        List<Long> catelogPath = new ArrayList<>();
        catelogPath = findParentPath(catelogId, catelogPath);

        Collections.reverse(catelogPath);

        return catelogPath.toArray(new Long[catelogPath.size()]);
    }

    /**
     * 清除多个指定的缓存
     *     @Caching(evict = {
     *             @CacheEvict(value = {"categoryJson"},key = "'getLevel1Categorys'"),
     *             @CacheEvict(value = {"categoryJson"},key = "'getCatalogJsonWithSpringCache'")
     *     })
     * 清楚同一分区下所有的缓存
     *     @CacheEvict(value = {"categoryJson"},allEntries = true)
     * @param category
     */

    @Override
    @Transactional
//    @Caching(evict = {
//            @CacheEvict(value = {"categoryJson"},key = "'getLevel1Categorys'"),
//            @CacheEvict(value = {"categoryJson"},key = "'getCatalogJsonWithSpringCache'")
//    })
    @CacheEvict(value = {"categoryJson"},allEntries = true)
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (StringUtils.isNotEmpty(category.getName())) {
//        更新中间表
            categoryBrandRelationService.updateCateGoryDetail(category.getCatId(), category.getName());
        }
//TODO:更新其他关联信息
    }

    @Cacheable(value = {"categoryJson"}, key = "#root.methodName",sync = true)
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        return baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>()
                .eq(CategoryEntity::getParentCid, 0));
    }

    /**
     * sync = true  本地同步锁
     * @return
     */
    @Cacheable(value = {"categoryJson"},key = "#root.methodName",sync = true)
    @Override
    public Map<String, List<Catalog2VO>> getCatalogJsonWithSpringCache() {
        return getCateGoryJsonWithSpringCache();
    }

    public Map<String,List<Catalog2VO>> getCateGoryJsonWithSpringCache(){
        // 2.查询所有分类，按照parentCid分组
        Map<Long, List<CategoryEntity>> categoryMap = baseMapper.selectList(null).stream()
                .collect(Collectors.groupingBy(key -> key.getParentCid()));

        // 3.获取1级分类
        List<CategoryEntity> level1Categorys = categoryMap.get(0L);

        // 4.封装数据
        Map<String, List<Catalog2VO>> result = level1Categorys.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), l1Category -> {
            // 5.查询2级分类，并封装成List<Catalog2VO>
            List<Catalog2VO> catalog2VOS = categoryMap.get(l1Category.getCatId())
                    .stream().map(l2Category -> {
                        // 7.查询3级分类，并封装成List<Catalog3VO>
                        List<Catalog2VO.Catalog3Vo> catalog3Vos = categoryMap.get(l2Category.getCatId())
                                .stream().map(l3Category -> {
                                    // 封装3级分类VO
                                    Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2Category.getCatId().toString(), l3Category.getCatId().toString(), l3Category.getName());
                                    return catalog3Vo;
                                }).collect(Collectors.toList());
                        // 封装2级分类VO返回
                        Catalog2VO catalog2VO = new Catalog2VO(l1Category.getCatId().toString(), catalog3Vos, l2Category.getCatId().toString(), l2Category.getName());
                        return catalog2VO;
                    }).collect(Collectors.toList());
            return catalog2VOS;
        }));
        System.out.printf("查询数据库.....");
        String jsonString = JSON.toJSONString(result);
        return result;
    }

    private Map<String, List<Catalog2VO>> getCatalogJsonWithRedisAndRedissonLock() {
        String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
        if (StringUtils.isEmpty(categoryJSON)) {
//            缓存未命中
            System.out.printf("缓存未命中......");
            return getCategoryRedissonLock();

        }
        System.out.printf("缓存命中......");
        Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return map;
    }

    private Map<String, List<Catalog2VO>> getCategoryRedissonLock() {

//        使用Redisson获取分布式锁
        RLock rLock = redissonClient.getLock("RedissonLockCategoryJson");
        //        设置成功证明抢到锁
        rLock.lock();
        if(!rLock.isLocked()){
            try {
                Thread.sleep(100);
                getCategoryRedissonLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
//        try {
////            测试看门狗机制，断点测试看门狗会导致看门狗机制失效
//            Thread.sleep(35000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
        if (StringUtils.isNotEmpty(categoryJSON)) {
            Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
            });
            return map;
        }
        // 2.查询所有分类，按照parentCid分组
        Map<Long, List<CategoryEntity>> categoryMap = baseMapper.selectList(null).stream()
                .collect(Collectors.groupingBy(key -> key.getParentCid()));

        // 3.获取1级分类
        List<CategoryEntity> level1Categorys = categoryMap.get(0L);

        // 4.封装数据
        Map<String, List<Catalog2VO>> result = level1Categorys.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), l1Category -> {
            // 5.查询2级分类，并封装成List<Catalog2VO>
            List<Catalog2VO> catalog2VOS = categoryMap.get(l1Category.getCatId())
                    .stream().map(l2Category -> {
                        // 7.查询3级分类，并封装成List<Catalog3VO>
                        List<Catalog2VO.Catalog3Vo> catalog3Vos = categoryMap.get(l2Category.getCatId())
                                .stream().map(l3Category -> {
                                    // 封装3级分类VO
                                    Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2Category.getCatId().toString(), l3Category.getCatId().toString(), l3Category.getName());
                                    return catalog3Vo;
                                }).collect(Collectors.toList());
                        // 封装2级分类VO返回
                        Catalog2VO catalog2VO = new Catalog2VO(l1Category.getCatId().toString(), catalog3Vos, l2Category.getCatId().toString(), l2Category.getName());
                        return catalog2VO;
                    }).collect(Collectors.toList());
            return catalog2VOS;
        }));
        System.out.printf("查询数据库.....");
        String jsonString = JSON.toJSONString(result);
        stringRedisTemplate.opsForValue().set(CategoryConstant.LOCK_KEY_CATALOG_JSON, jsonString);
//        释放锁
        rLock.unlock();
        return result;
    }

    /**
     * redis实现分布式锁
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalogJsonWithRedisAndRedisLock() {
        String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
        if (StringUtils.isEmpty(categoryJSON)) {
//            缓存未命中
            System.out.printf("缓存未命中......");
            return getCategoryRedisLock();

        }
        System.out.printf("缓存命中......");
        Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return map;
    }

    /**
     * 本地锁
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalogJsonWithRedisAndLocalLock() {
        String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
        if (StringUtils.isEmpty(categoryJSON)) {
//            缓存未命中
            System.out.printf("缓存未命中......");
            return getCategoryLocalLock();

        }
        System.out.printf("缓存命中......");
        Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return map;
    }

    @NotNull
    private Map<String, List<Catalog2VO>> getCategoryRedisLock() {

//        使用setnx获取分布式锁
        String uuid = UUID.randomUUID().toString();
//        设置成功证明抢到锁
        Boolean setIfAbsent = stringRedisTemplate.opsForValue().setIfAbsent("RedisSetNExLock", uuid, 300, TimeUnit.SECONDS);
        if (!setIfAbsent) {
            try {
                Thread.sleep(100);
                getCategoryRedisLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
            if (StringUtils.isNotEmpty(categoryJSON)) {
                Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
                return map;
            }
            // 2.查询所有分类，按照parentCid分组
            Map<Long, List<CategoryEntity>> categoryMap = baseMapper.selectList(null).stream()
                    .collect(Collectors.groupingBy(key -> key.getParentCid()));

            // 3.获取1级分类
            List<CategoryEntity> level1Categorys = categoryMap.get(0L);

            // 4.封装数据
            Map<String, List<Catalog2VO>> result = level1Categorys.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), l1Category -> {
                // 5.查询2级分类，并封装成List<Catalog2VO>
                List<Catalog2VO> catalog2VOS = categoryMap.get(l1Category.getCatId())
                        .stream().map(l2Category -> {
                            // 7.查询3级分类，并封装成List<Catalog3VO>
                            List<Catalog2VO.Catalog3Vo> catalog3Vos = categoryMap.get(l2Category.getCatId())
                                    .stream().map(l3Category -> {
                                        // 封装3级分类VO
                                        Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2Category.getCatId().toString(), l3Category.getCatId().toString(), l3Category.getName());
                                        return catalog3Vo;
                                    }).collect(Collectors.toList());
                            // 封装2级分类VO返回
                            Catalog2VO catalog2VO = new Catalog2VO(l1Category.getCatId().toString(), catalog3Vos, l2Category.getCatId().toString(), l2Category.getName());
                            return catalog2VO;
                        }).collect(Collectors.toList());
                return catalog2VOS;
            }));
            System.out.printf("查询数据库.....");
            String jsonString = JSON.toJSONString(result);
            stringRedisTemplate.opsForValue().set(CategoryConstant.LOCK_KEY_CATALOG_JSON, jsonString);
            return result;
        } finally {
//        删除分布式锁
            // 封装lua脚本（原子操作解锁）
            // 查询+删除（当前值与目标值是否相等，相等执行删除，不等返回0）
            String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                    "then\n" +
                    "    return redis.call('del',KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            // 删除锁
            stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("RedisSetNExLock"), uuid);
        }

    }

    @NotNull
    private Map<String, List<Catalog2VO>> getCategoryLocalLock() {
        synchronized (this) {
            String categoryJSON = stringRedisTemplate.opsForValue().get(CategoryConstant.LOCK_KEY_CATALOG_JSON);
            if (StringUtils.isNotEmpty(categoryJSON)) {
                Map<String, List<Catalog2VO>> map = JSON.parseObject(categoryJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
                return map;
            }
            // 2.查询所有分类，按照parentCid分组
            Map<Long, List<CategoryEntity>> categoryMap = baseMapper.selectList(null).stream()
                    .collect(Collectors.groupingBy(key -> key.getParentCid()));

            // 3.获取1级分类
            List<CategoryEntity> level1Categorys = categoryMap.get(0L);

            // 4.封装数据
            Map<String, List<Catalog2VO>> result = level1Categorys.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), l1Category -> {
                // 5.查询2级分类，并封装成List<Catalog2VO>
                List<Catalog2VO> catalog2VOS = categoryMap.get(l1Category.getCatId())
                        .stream().map(l2Category -> {
                            // 7.查询3级分类，并封装成List<Catalog3VO>
                            List<Catalog2VO.Catalog3Vo> catalog3Vos = categoryMap.get(l2Category.getCatId())
                                    .stream().map(l3Category -> {
                                        // 封装3级分类VO
                                        Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2Category.getCatId().toString(), l3Category.getCatId().toString(), l3Category.getName());
                                        return catalog3Vo;
                                    }).collect(Collectors.toList());
                            // 封装2级分类VO返回
                            Catalog2VO catalog2VO = new Catalog2VO(l1Category.getCatId().toString(), catalog3Vos, l2Category.getCatId().toString(), l2Category.getName());
                            return catalog2VO;
                        }).collect(Collectors.toList());
                return catalog2VOS;
            }));
            System.out.printf("查询数据库.....");
            String jsonString = JSON.toJSONString(result);
            stringRedisTemplate.opsForValue().set(CategoryConstant.LOCK_KEY_CATALOG_JSON, jsonString);
            return result;
        }
    }

    private List<Long> findParentPath(Long catelogId, List<Long> catelogPath) {
        CategoryEntity category = this.getById(catelogId);
        catelogPath.add(category.getCatId());
        if (category.getParentCid() != 0) {
            findParentPath(category.getParentCid(), catelogPath);
        }
        return catelogPath;
    }


    private List<CategoryEntity> getChildRens(CategoryEntity root, List<CategoryEntity> entityList) {
        List<CategoryEntity> collect = entityList.stream()
                .filter(item -> root.getCatId().equals(item.getParentCid()))
                .map(item -> {
                    item.setChildren(getChildRens(item, entityList));
                    return item;
                }).sorted((a1, a2) -> {
                    return (a1.getSort() == null ? 0 : a1.getSort()) - (a2.getSort() == null ? 0 : a2.getSort());
                })
                .collect(Collectors.toList());
        return collect;
    }
}
